package com.company.finance.ddd.domain.core.voucher.rule;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.text.MessageFormat;
import java.util.*;

/**
 *
 * @author wanglei
 * @version $$Id: RuleConfig, v 0.1 2021/2/2 11:36 wanglei Exp $$
 */
@Configuration
public class RuleConfigurationLoader implements ApplicationContextAware {

    private static Map<RuleDefinition, RuleHolder> ruleIdentifyingRuleMap;
    private static Map<String, Set<VoucherType>> billSubTypeCodeVoucherTypeMap;

    public static RuleHolder getRule(RuleDefinition ruleDefinition) {
        RuleHolder rule = ruleIdentifyingRuleMap.get(ruleDefinition);
        Assert.notNull(rule, MessageFormat.format("Can not get ruleItem by ruleIdentifying[{0}]", ruleDefinition));
        return rule;
    }

    /**
     * 根据单据类型获取{@link RuleHolder}的所有子类所定义的凭证生成规则中对应的凭证类型
     * @param billSubTypeCode 单据类型
     * @return Set<VoucherType>
     */
    public static Set<VoucherType> getVoucherTypes(String billSubTypeCode) {
        Set<VoucherType> set = billSubTypeCodeVoucherTypeMap.get(billSubTypeCode);
        if (CollectionUtils.isEmpty(set)) {
            throw new NoMatchVoucherTypeException(billSubTypeCode);
        }
        return set;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, RuleHolder> rules = applicationContext.getBeansOfType(RuleHolder.class);

        Map<RuleDefinition, RuleHolder> ruleMap = new HashMap<>(rules.size());
        Map<String, Set<VoucherType>> billSubTypeMap = new HashMap<>(rules.size() << 3);

        rules.forEach((beanName, ruleBean) -> {
            RuleDefinition ruleDefinition = ruleBean.getRuleDefinition();
            if (null != ruleMap.put(ruleDefinition, ruleBean)) {
                throw new DuplicateRuleException(ruleDefinition);
            }
            ruleBean.getRuleDefinition().getBillSpecifications()
                    .forEach(billSpecification -> {
                        if (CollectionUtils.isEmpty(billSubTypeMap.get(billSpecification.getBillSubTypeEnum().subTypeCode))) {
                            billSubTypeMap.put(billSpecification.getBillSubTypeEnum().subTypeCode, new HashSet<>());
                        }
                        billSubTypeMap.get(billSpecification.getBillSubTypeEnum().subTypeCode)
                                .add(ruleBean.getRuleDefinition().getVoucherType());
                    });

        });
        ruleIdentifyingRuleMap = Collections.unmodifiableMap(ruleMap);
        billSubTypeCodeVoucherTypeMap = Collections.unmodifiableMap(billSubTypeMap);
    }

    public static class DuplicateRuleException extends RuntimeException {
        public DuplicateRuleException(RuleDefinition ruleDefinition) {
            super(MessageFormat.format("The rule is duplicate. [{0}]", ruleDefinition));
        }

        @Override
        public synchronized Throwable fillInStackTrace() {
            setStackTrace(new StackTraceElement[0]);
            return this;
        }
    }

    public static class NoMatchVoucherTypeException extends RuntimeException {
        public NoMatchVoucherTypeException(String billSubTypeCode) {
            super(MessageFormat.format("Can not get VoucherType by BillSubType[{0}]"
                    , billSubTypeCode));
        }

        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }
    }
}
